#include "lightSprite.h"
#include "components/3D/sweety_3D_Camera.h"
#include "components/3D/sweety_3D_Material.h"
#include "components/3D/lightSources/sweety_3D_Pointlight.h"
#include "framework/sweety_Render.h"
#include "third-party/glfw/include/glfw3.h"

#define SHADER_PATH "Shaders/"
#define IMAGE_PATH "Resourses/images/"

namespace SW
{
    float vtx[] = {
        // positions          // normals           // texture coords
        -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f, 0.0f,
         0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f, 0.0f,
         0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f, 1.0f,
         0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f, 1.0f,
        -0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f, 0.0f,

        -0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,   0.0f, 0.0f,
         0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,   1.0f, 0.0f,
         0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,   1.0f, 1.0f,
         0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,   1.0f, 1.0f,
        -0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,   0.0f, 1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,   0.0f, 0.0f,

        -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,  1.0f, 0.0f,
        -0.5f,  0.5f, -0.5f, -1.0f,  0.0f,  0.0f,  1.0f, 1.0f,
        -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,  0.0f, 1.0f,
        -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,  0.0f, 1.0f,
        -0.5f, -0.5f,  0.5f, -1.0f,  0.0f,  0.0f,  0.0f, 0.0f,
        -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,  1.0f, 0.0f,

         0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  1.0f, 0.0f,
         0.5f,  0.5f, -0.5f,  1.0f,  0.0f,  0.0f,  1.0f, 1.0f,
         0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,  0.0f, 1.0f,
         0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,  0.0f, 1.0f,
         0.5f, -0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  0.0f, 0.0f,
         0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  1.0f, 0.0f,

        -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,  0.0f, 1.0f,
         0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,  1.0f, 1.0f,
         0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,  1.0f, 0.0f,
         0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,  1.0f, 0.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,  0.0f, 0.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,  0.0f, 1.0f,

        -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  0.0f, 1.0f,
         0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  1.0f, 1.0f,
         0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  1.0f, 0.0f,
         0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  1.0f, 0.0f,
        -0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  0.0f, 0.0f,
        -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  0.0f, 1.0f
    };

    vec3 cubePositions[] = {
       vec3(0.0f,  0.0f,  0.0f),
       vec3(2.0f,  5.0f, -15.0f),
       vec3(-1.5f, -2.2f, -2.5f),
       vec3(-3.8f, -2.0f, -12.3f),
       vec3(2.4f, -0.4f, -3.5f),
       vec3(-1.7f,  3.0f, -7.5f),
       vec3(1.3f, -2.0f, -2.5f),
       vec3(1.5f,  2.0f, -2.5f),
       vec3(1.5f,  0.2f, -1.5f),
       vec3(-1.3f,  1.0f, -1.5f)
    };

    //// Positions of the point lights
    //vec3 pointLightPositions[] = {
    //    vec3(0.7f,  0.2f,  2.0f),
    //    vec3(2.3f, -3.3f, -4.0f),
    //    vec3(-4.0f,  2.0f, -12.0f),
    //    vec3(0.0f,  0.0f, -3.0f),
    //    vec3(0.0f,  3.0f, -3.0f)
    //};

    vec3 lightPos(-0.2f, -1.0f, -0.3f);


    LightSprite::LightSprite() : m_PointlightNum(0)
    {       
        if (nullptr == m_VaoPtr)
        {
            m_VaoPtr = new VaoClass(vtx, sizeof(vtx), 8, nullptr, 0, false, true, true);
        }

        if (nullptr == m_LightVaoPtr)
        {
            m_LightVaoPtr = new VaoClass(vtx, sizeof(vtx), 8);
        }

        if (nullptr == m_Material)
        {
            m_Material = new Material;
        }
                     
        InitShaders();        
    }

    LightSprite::~LightSprite()
    {
        if (m_VaoPtr)
        {
            delete m_VaoPtr;
            m_VaoPtr = nullptr;
        }

        if (m_LightVaoPtr)
        {
            delete m_LightVaoPtr;
            m_LightVaoPtr = nullptr;
        }

        if (m_Material)
        {
            delete m_Material;
            m_Material = nullptr;
        }

        for (auto it = m_Pointlights.begin(); it != m_Pointlights.end(); ++it) 
        {
            delete it->second;
            it->second = nullptr;
        }
        m_Pointlights.clear();

    }

    void LightSprite::InitShaders()
    {
        InitLightShaders();

        InitLampShaders();
    }

    void LightSprite::InitLightShaders()
    {
        m_LightShader.useVertex(); m_LightShader.useFragment();

        m_LightShader.getVertex()->Load("", 1, SHADER_PATH "lightSpriteVert.glsl");

        m_LightShader.getFragment()->Load("", 1, SHADER_PATH "lightSpriteFrag.glsl");

        // The vertex and fragment are added to the program object
        m_LightShader.addVertex();
        m_LightShader.addFragment();

        m_LightShader.link();

        m_LightShader.bindPipeline();
        m_LightShader.useProgram();  

        m_Material->Sampler(&m_LightShader, IMAGE_PATH "container2.jpg", IMAGE_PATH "container2_specular.jpg", 32.0f);

        m_LightShader.reset();
    }

    void LightSprite::InitLampShaders()
    {
        m_LampShader.useVertex(); m_LampShader.useFragment();

        m_LampShader.getVertex()->Load("", 1, SHADER_PATH "lampSpriteVert.glsl");

        m_LampShader.getFragment()->Load("", 1, SHADER_PATH "lampSpriteFrag.glsl");

        // The vertex and fragment are added to the program object
        m_LampShader.addVertex();
        m_LampShader.addFragment();

        m_LampShader.link();

        m_LampShader.bindPipeline();
        m_LampShader.useProgram();

        m_LampShader.reset();
    }   

    void LightSprite::InitLights()
    {
        InitPointlights(5);
    }

    void LightSprite::InitPointlights(unsigned int number)
    {
      
    }

    void LightSprite::Render(const mat4& modelTrans)
    {
        if (!m_VaoPtr || !m_LightVaoPtr || !m_Material)
        {
            return;
        }

        Camera3D* camera = dynamic_cast<Camera3D*>(Renderer::GetCurrentCamera());
        if (!camera)
        {
            return;
        }

        if (!m_IsResetCampos)
        {
            camera->SetPos(vec3(0.f, 0.f, 4.f));
            m_IsResetCampos = true;
        }

        vec3 camPos = camera->GetPosition();
      
        GLuint Model_SL = m_LightShader.getUniformLocation("model");
        GLuint View_SL = m_LightShader.getUniformLocation("view");
        GLuint Projection_SL = m_LightShader.getUniformLocation("projection");
               
        // Create camera transformations
        mat4 view(1.0f), projection(1.0f);
        view = camera->GetViewTrans();
        projection = camera->GetProjectionTrans();

        {   // light shader
            m_LightShader.bindPipeline();
            m_LightShader.useProgram();   

            m_VaoPtr->bindVao();
                      
            // Set camera position 
            glUniform3fv(m_LightShader.getUniformLocation("cameraPos"), 1, value_ptr(camPos));
                                 
            // Set material properties              
            m_Material->Set(&m_LightShader, "material.diffuse", Material::SamplerType::DIFFUSE);
            m_Material->Set(&m_LightShader, "material.specular", Material::SamplerType::SPECULAR);
            m_Material->SetShininess(&m_LightShader, "material.shininess", 32.0f);

            // Directional light
            glUniform3fv(m_LightShader.getUniformLocation("dirLight.direction"), 1, value_ptr(vec3(-0.2f, -1.0f, -0.3f)));
            glUniform3fv(m_LightShader.getUniformLocation("dirLight.ambient"), 1, value_ptr(vec3(0.05f, 0.05f, 0.05f)));
            glUniform3fv(m_LightShader.getUniformLocation("dirLight.diffuse"), 1, value_ptr(vec3(0.4f, 0.4f, 0.4f)));
            glUniform3fv(m_LightShader.getUniformLocation("dirLight.specular"), 1, value_ptr(vec3(0.5f, 0.5f, 0.5f)));
           
            glUniform1i(m_LightShader.getUniformLocation("nr_point_light_num"), m_PointlightNum);

            char pointLightbuf[32];
            int i = 0;
            std::map<unsigned int, PointLight*>::iterator iter = m_Pointlights.begin();
            for (; iter != m_Pointlights.end(); iter++, i++)
            {
                sprintf(pointLightbuf, "pointLights[%d].position", i);
                iter->second->SetPosition(iter->second->GetPosition(), pointLightbuf);

                sprintf(pointLightbuf, "pointLights[%d].ambient", i);
                iter->second->SetAmbient(iter->second->GetAmbient(), pointLightbuf);

                sprintf(pointLightbuf, "pointLights[%d].diffuse", i);
                iter->second->SetDiffuse(iter->second->GetDiffuse(), pointLightbuf);

                sprintf(pointLightbuf, "pointLights[%d].specular", i);
                iter->second->SetSpecular(iter->second->GetSpecular(), pointLightbuf);

                sprintf(pointLightbuf, "pointLights[%d].constant", i);
                iter->second->SetConstant(iter->second->GetConstant(), pointLightbuf);

                sprintf(pointLightbuf, "pointLights[%d].linear", i);
                iter->second->SetLinear(iter->second->GetLinear(), pointLightbuf);

                sprintf(pointLightbuf, "pointLights[%d].quadratic", i);
                iter->second->SetQuadratic(iter->second->GetQuadratic(), pointLightbuf);
            }        

            // Bind diffuse map           
            m_Material->Bind(Material::SamplerType::DIFFUSE);

            // Bind specular map           
            m_Material->Bind(Material::SamplerType::SPECULAR);

            // MVP
            glUniformMatrix4fv(View_SL, 1, GL_FALSE, value_ptr(view));
            glUniformMatrix4fv(Projection_SL, 1, GL_FALSE, value_ptr(projection));
            for (GLuint i = 0; i < 10; i++)
            {
                // Calculate the model matrix for each object and pass it to shader before drawing   
                mat4 model(1.f);
              
                model = translate(model, cubePositions[i]);
                GLfloat angle = 20.0f * i;
                model = rotate(model, angle, vec3(1.0f, 0.3f, 0.5f));
                glUniformMatrix4fv(Model_SL, 1, GL_FALSE, value_ptr(model));
                m_VaoPtr->draw(false, false);               
            }            
            m_LightShader.reset();
        }

        {   // light shader
            m_LampShader.bindPipeline();
            m_LampShader.useProgram();
            m_LightVaoPtr->bindVao();
                        
            glUniformMatrix4fv(View_SL, 1, GL_FALSE, value_ptr(view));
            glUniformMatrix4fv(Projection_SL, 1, GL_FALSE, value_ptr(projection));

            std::map<unsigned int, PointLight*>::iterator iter = m_Pointlights.begin();
            for (; iter != m_Pointlights.end(); iter++)
            {
                mat4 model(1.f);
                model = translate(model, iter->second->GetPosition());
                model = scale(model, vec3(0.2f)); // Make it a smaller cube           
                glUniformMatrix4fv(Model_SL, 1, GL_FALSE, value_ptr(model));
                m_LightVaoPtr->draw(false, false);
            }

            m_LampShader.reset();
        }
        
    }

    void LightSprite::AddPointlight(const char* name)
    {
        SW_ASSERT(name);

        PointLight* lightP = new PointLight(&m_LightShader);
        if (lightP)
        {      
            std::string timeNow = std::to_string(glfwGetTime());
            timeNow += name;
            unsigned int id = SW::GetID(timeNow.c_str());            

            if (m_Pointlights.find(id) == m_Pointlights.end())
            {
                lightP->SetId(id);
                lightP->SetName(name);
                lightP->SetPosition(vec3(0.7f, 0.2f, 2.0f));

                m_Pointlights[id] = lightP;        
                m_PointlightNum = m_Pointlights.size();
            }
        }
    }

    int LightSprite::GetPointlightNumber() const
    {
        return m_PointlightNum;
    }
}